/**
 * Development repository: https://github.com/kaisalmen/WWOBJLoader
 */

const CodeSerializer = {

  /**
   * Serialize an object with specific prototype definition.
   *
   * @param {Object} targetPrototype The object that should be serialized
   * @param {Object} targetPrototypeInstance An instance of the oriobject that should be serialized
   * @param {String} [basePrototypeName] Name of the prototype
   * @param {Object} [overrideFunctions} Array of {@Link CodeSerializationInstruction} allows to replace or remove function with provided content
   *
   * @returns {String}
   */
  serializeClass: function (targetPrototype, targetPrototypeInstance, basePrototypeName, overrideFunctions) {

    let objectPart, constructorString, i, funcInstructions, funcTemp;
    const fullObjectName = targetPrototypeInstance.constructor.name;
    const prototypeFunctions = [];
    const objectProperties = [];
    const objectFunctions = [];
    const isExtended = (basePrototypeName !== null && basePrototypeName !== undefined);

    if (!Array.isArray(overrideFunctions)) overrideFunctions = [];

    for (const name in targetPrototype.prototype) {

      objectPart = targetPrototype.prototype[name];
      funcInstructions = new CodeSerializationInstruction(name, fullObjectName + '.prototype.' + name);
      funcInstructions.setCode(objectPart.toString());

      if (name === 'constructor') {

        if (!funcInstructions.isRemoveCode()) {

          constructorString = fullObjectName + ' = ' + funcInstructions.getCode() + ';\n\n';

        }

      } else if (typeof objectPart === 'function') {

        funcTemp = overrideFunctions[name];

        if (funcTemp instanceof CodeSerializationInstruction && funcTemp.getName() === funcInstructions.getName()) {

          funcInstructions = funcTemp;

        }

        if (!funcInstructions.isRemoveCode()) {

          if (isExtended) {

            prototypeFunctions.push(funcInstructions.getFullName() + ' = ' + funcInstructions.getCode() + ';\n\n');

          } else {

            prototypeFunctions.push('\t' + funcInstructions.getName() + ': ' + funcInstructions.getCode() + ',\n\n');

          }

        }

      }

    }

    for (const name in targetPrototype) {

      objectPart = targetPrototype[name];
      funcInstructions = new CodeSerializationInstruction(name, fullObjectName + '.' + name);

      if (typeof objectPart === 'function') {

        funcTemp = overrideFunctions[name];
        if (funcTemp instanceof CodeSerializationInstruction && funcTemp.getName() === funcInstructions.getName()) {

          funcInstructions = funcTemp;

        } else {

          funcInstructions.setCode(objectPart.toString());

        }

        if (!funcInstructions.isRemoveCode()) {

          objectFunctions.push(funcInstructions.getFullName() + ' = ' + funcInstructions.getCode() + ';\n\n');

        }

      } else {

        if (typeof (objectPart) === 'string' || objectPart instanceof String) {

          funcInstructions.setCode('\"' + objectPart.toString() + '\"');

        } else if (typeof objectPart === 'object') {

          console.log('Omitting object "' + funcInstructions.getName() + '" and replace it with empty object.');
          funcInstructions.setCode("{}");

        } else {

          funcInstructions.setCode(objectPart);

        }

        if (!funcInstructions.isRemoveCode()) {

          objectProperties.push(funcInstructions.getFullName() + ' = ' + funcInstructions.getCode() + ';\n');

        }

      }

    }

    let objectString = constructorString + '\n\n';

    if (isExtended) {

      objectString += fullObjectName + '.prototype = Object.create( ' + basePrototypeName + '.prototype );\n';

    }

    objectString += fullObjectName + '.prototype.constructor = ' + fullObjectName + ';\n';
    objectString += '\n\n';

    for (i = 0; i < objectProperties.length; i++) {

      objectString += objectProperties[i];

    }

    objectString += '\n\n';

    for (i = 0; i < objectFunctions.length; i++) {

      objectString += objectFunctions[i];

    }

    objectString += '\n\n';

    if (isExtended) {

      for (i = 0; i < prototypeFunctions.length; i++) {

        objectString += prototypeFunctions[i];

      }

    } else {

      objectString += fullObjectName + '.prototype = {\n\n';
      for (i = 0; i < prototypeFunctions.length; i++) {

        objectString += prototypeFunctions[i];

      }

      objectString += '\n};';

    }

    objectString += '\n\n';

    return objectString;

  },
};

/**
 * Allows to define instructions to override or remove
 * @param {String} name Usually the name of a function
 * @param {String} fullName The name plus full object description
 * @constructor
 */
const CodeSerializationInstruction = function (name, fullName) {

  this.name = name;
  this.fullName = fullName;
  this.code = null;
  this.removeCode = false;

};

CodeSerializationInstruction.prototype = {

  constructor: CodeSerializationInstruction,

  /**
   * Returns the name of the function
   * @return {String}
   */
  getName: function () {

    return this.name;

  },

  /**
   * Returns the full name of the function
   * @return {String}
   */
  getFullName: function () {

    return this.fullName;

  },

  /**
   * Set the string containing the serialized function
   * @param {String} code
   * @return {CodeSerializationInstruction}
   */
  setCode: function (code) {

    this.code = code;
    return this;

  },

  /**
   * Returns the serialized function code
   * @return {String}
   */
  getCode: function () {

    return this.code;

  },

  /**
   * Set if function should be removed
   * @param {boolean} removeCode
   * @return {CodeSerializationInstruction}
   */
  setRemoveCode: function (removeCode) {

    this.removeCode = removeCode;
    return this;

  },

  /**
   * If function should be completely removed
   * @return {boolean}
   */
  isRemoveCode: function () {

    return this.removeCode;

  }

};

export {
  CodeSerializer,
  CodeSerializationInstruction
};
